/*!
 * @author  Vladimir Kerimov
 * @file    fixed.h
 * @version 1.0
 * @brief   Fixed point value
 * @date    2012
 */

/*
 *    ______   __
 *   /  ___/  /__/   __  ___      _____        _____     __    __     _____        __  ___      _____     __  ___
 *  |  |___    __   /  |/   \    / __  \      /  _  \   |  |  |  |   /  _  \      |  |/   \    /  _  \   |  |/   \
 *   \___  \  /  |  |   __   |  | /  __/     |  / \  |  |  |  |  |  |  / \  |     |   __   |  |  / \  |  |   __   |
 *  ____/  /  |  |  |  /  \  |  |   /___     |  \_/  |  |   \_|  |  |  \_/  |     |  /  \  |  |  \_/  |  |  /  \  |
 * /______/   |__/  |__|  |__/   \_____/      \___   |   \___/|__|   \___/|_|     |__|  |__|   \_____/   |__|  |__|
 *                                                |  |
 *                                                |__/
 *
 */

#pragma once

#include <qua/api.h>
#include <cstdint>

namespace qua
{
    class QUAZAR_API fixed
    {
    public:
        fixed();

        typedef int prec_t;
        static const prec_t prec_auto = -1;

        fixed( double   value, prec_t prec = prec_auto );
        fixed( float    value, prec_t prec = prec_auto );
        fixed( int64_t  value, prec_t prec = prec_auto );
        fixed( int32_t  value, prec_t prec = prec_auto );
        fixed( int16_t  value, prec_t prec = prec_auto );
        fixed( int8_t   value, prec_t prec = prec_auto );
        fixed( uint64_t value, prec_t prec = prec_auto );
        fixed( uint32_t value, prec_t prec = prec_auto );
        fixed( uint16_t value, prec_t prec = prec_auto );
        fixed( uint8_t  value, prec_t prec = prec_auto );
        fixed( bool     value, prec_t prec = prec_auto );

        fixed& operator = ( double   value );
        fixed& operator = ( float    value );
        fixed& operator = ( int64_t  value );
        fixed& operator = ( int32_t  value );
        fixed& operator = ( int16_t  value );
        fixed& operator = ( int8_t   value );
        fixed& operator = ( uint64_t value );
        fixed& operator = ( uint32_t value );
        fixed& operator = ( uint16_t value );
        fixed& operator = ( uint8_t  value );
        fixed& operator = ( bool     value );

        operator double() const;
        operator float() const;
        operator int64_t() const;
        operator int32_t() const;
        operator int16_t() const;
        operator int8_t() const;
        operator uint64_t() const;
        operator uint32_t() const;
        operator uint16_t() const;
        operator uint8_t() const;
        operator bool() const;

        inline prec_t prec() const;

        void set_prec( prec_t );

        inline double   frac() const;
        inline uint64_t mulfrac() const;
        inline fixed    abs() const;
        inline int64_t  floor() const;
        inline int64_t  ceil() const;

        inline void set_floor( int64_t value );
        inline void set_mulfrac( uint64_t value );

        static uint64_t divider();
        static double   multiplier();
        static prec_t   max_prec();

        fixed reverse() const;

        fixed& operator += ( const fixed& );
        fixed& operator -= ( const fixed& );
        fixed& operator *= ( const fixed& );
        fixed& operator /= ( const fixed& );

        fixed operator + ( const fixed& ) const;
        fixed operator - ( const fixed& ) const;
        fixed operator * ( const fixed& ) const;
        fixed operator / ( const fixed& ) const;

        inline fixed operator + () const;
               fixed operator - () const;

        inline bool operator !  () const;
        inline bool operator == ( const fixed& ) const;
        inline bool operator != ( const fixed& ) const;
        inline bool operator <= ( const fixed& ) const;
        inline bool operator <  ( const fixed& ) const;
        inline bool operator >= ( const fixed& ) const;
        inline bool operator >  ( const fixed& ) const;

        inline fixed& operator += ( double );
        inline fixed& operator -= ( double );
        inline fixed& operator *= ( double );
        inline fixed& operator /= ( double );

        inline fixed operator + ( double ) const;
        inline fixed operator - ( double ) const;
        inline fixed operator * ( double ) const;
        inline fixed operator / ( double ) const;

        inline fixed& operator += ( float );
        inline fixed& operator -= ( float );
        inline fixed& operator *= ( float );
        inline fixed& operator /= ( float );

        inline fixed operator + ( float ) const;
        inline fixed operator - ( float ) const;
        inline fixed operator * ( float ) const;
        inline fixed operator / ( float ) const;

        inline fixed& operator += ( int64_t );
        inline fixed& operator -= ( int64_t );
        inline fixed& operator *= ( int64_t );
        inline fixed& operator /= ( int64_t );

        inline fixed operator + ( int64_t ) const;
        inline fixed operator - ( int64_t ) const;
        inline fixed operator * ( int64_t ) const;
        inline fixed operator / ( int64_t ) const;

        inline fixed& operator += ( uint64_t );
        inline fixed& operator -= ( uint64_t );
        inline fixed& operator *= ( uint64_t );
        inline fixed& operator /= ( uint64_t );

        inline fixed operator + ( uint64_t ) const;
        inline fixed operator - ( uint64_t ) const;
        inline fixed operator * ( uint64_t ) const;
        inline fixed operator / ( uint64_t ) const;

        inline fixed& operator += ( int32_t );
        inline fixed& operator -= ( int32_t );
        inline fixed& operator *= ( int32_t );
        inline fixed& operator /= ( int32_t );

        inline fixed operator + ( int32_t ) const;
        inline fixed operator - ( int32_t ) const;
        inline fixed operator * ( int32_t ) const;
        inline fixed operator / ( int32_t ) const;

        inline fixed& operator += ( uint32_t );
        inline fixed& operator -= ( uint32_t );
        inline fixed& operator *= ( uint32_t );
        inline fixed& operator /= ( uint32_t );

        inline fixed operator + ( uint32_t ) const;
        inline fixed operator - ( uint32_t ) const;
        inline fixed operator * ( uint32_t ) const;
        inline fixed operator / ( uint32_t ) const;

        inline bool operator == ( double ) const;
        inline bool operator != ( double ) const;
        inline bool operator <= ( double ) const;
        inline bool operator <  ( double ) const;
        inline bool operator >= ( double ) const;
        inline bool operator >  ( double ) const;

        inline bool operator == ( float ) const;
        inline bool operator != ( float ) const;
        inline bool operator <= ( float ) const;
        inline bool operator <  ( float ) const;
        inline bool operator >= ( float ) const;
        inline bool operator >  ( float ) const;

        inline bool operator == ( int64_t ) const;
        inline bool operator != ( int64_t ) const;
        inline bool operator <= ( int64_t ) const;
        inline bool operator <  ( int64_t ) const;
        inline bool operator >= ( int64_t ) const;
        inline bool operator >  ( int64_t ) const;

        inline bool operator == ( uint64_t ) const;
        inline bool operator != ( uint64_t ) const;
        inline bool operator <= ( uint64_t ) const;
        inline bool operator <  ( uint64_t ) const;
        inline bool operator >= ( uint64_t ) const;
        inline bool operator >  ( uint64_t ) const;

        inline bool operator == ( int32_t ) const;
        inline bool operator != ( int32_t ) const;
        inline bool operator <= ( int32_t ) const;
        inline bool operator <  ( int32_t ) const;
        inline bool operator >= ( int32_t ) const;
        inline bool operator >  ( int32_t ) const;

        inline bool operator == ( uint32_t ) const;
        inline bool operator != ( uint32_t ) const;
        inline bool operator <= ( uint32_t ) const;
        inline bool operator <  ( uint32_t ) const;
        inline bool operator >= ( uint32_t ) const;
        inline bool operator >  ( uint32_t ) const;

    private:
        int64_t     m_int;      //!< integral part may be negative
        uint64_t    m_frac;     //!< fractional part represented by decimal value after point
        prec_t      m_prec;     //!< precision for cast to string and output

        void normalize();
        void normfloat();
    };

    extern const uint64_t FIXED_POW10[];
    extern const uint64_t FIXED_HALF_POW10[];
    extern const uint64_t FIXED_DIVIDER;
    extern const uint64_t FIXED_HALFDIV;
    extern const uint64_t FIXED_SQRTDIV;
    extern const uint64_t FIXED_EPSILON;
    extern const uint64_t FIXED_EPSFLOAT;
    extern const int FIXED_MAX_PRECISION;

    inline int fixed::prec() const
    {
        return m_prec;
    }

    inline double fixed::frac() const
    {
        return m_frac * multiplier();
    }

    inline uint64_t fixed::mulfrac() const
    {
        return m_frac;
    }

    inline fixed fixed::abs() const
    {
        return m_int >= 0 ? *this : -*this;
    }

    inline int64_t fixed::floor() const
    {
        return m_int;
    }

    inline int64_t fixed::ceil() const
    {
        return m_frac ? m_int + 1 : m_int;
    }

    inline void fixed::set_floor( int64_t value )
    {
        m_int = value;
    }

    inline void fixed::set_mulfrac( uint64_t value )
    {
        m_frac = value;
    }

    inline fixed fixed::operator + () const
    {
        return *this;
    }

    inline bool fixed::operator ! () const
    {
        return !m_int && !m_frac;
    }

    inline bool fixed::operator == ( const fixed& arg ) const
    {
        return m_int == arg.m_int && m_frac == arg.m_frac;
    }

    inline bool fixed::operator != ( const fixed& arg ) const
    {
        return !( *this == arg );
    }

    inline bool fixed::operator < ( const fixed& arg ) const
    {
        return m_int < arg.m_int || (m_int == arg.m_int && m_frac < arg.m_frac);
    }

    inline bool fixed::operator <= ( const fixed& arg ) const
    {
        return m_int < arg.m_int || (m_int == arg.m_int && m_frac <= arg.m_frac);
    }

    inline bool fixed::operator > ( const fixed& arg ) const
    {
        return arg < *this;
    }

    inline bool fixed::operator >= ( const fixed& arg ) const
    {
        return arg <= *this;
    }

    inline fixed& fixed::operator += ( double arg ) { return ( *this += fixed(arg) ); }
    inline fixed& fixed::operator -= ( double arg ) { return ( *this -= fixed(arg) ); }
    inline fixed& fixed::operator *= ( double arg ) { return ( *this *= fixed(arg) ); }
    inline fixed& fixed::operator /= ( double arg ) { return ( *this /= fixed(arg) ); }

    inline fixed fixed::operator + ( double arg ) const { return *this + fixed(arg); }
    inline fixed fixed::operator - ( double arg ) const { return *this - fixed(arg); }
    inline fixed fixed::operator * ( double arg ) const { return *this * fixed(arg); }
    inline fixed fixed::operator / ( double arg ) const { return *this / fixed(arg); }

    inline fixed& fixed::operator += ( float arg ) { return ( *this += fixed(arg) ); }
    inline fixed& fixed::operator -= ( float arg ) { return ( *this -= fixed(arg) ); }
    inline fixed& fixed::operator *= ( float arg ) { return ( *this *= fixed(arg) ); }
    inline fixed& fixed::operator /= ( float arg ) { return ( *this /= fixed(arg) ); }

    inline fixed fixed::operator + ( float arg ) const { return *this + fixed(arg); }
    inline fixed fixed::operator - ( float arg ) const { return *this - fixed(arg); }
    inline fixed fixed::operator * ( float arg ) const { return *this * fixed(arg); }
    inline fixed fixed::operator / ( float arg ) const { return *this / fixed(arg); }

    inline fixed& fixed::operator += ( int64_t arg ) { return ( *this += fixed(arg) ); }
    inline fixed& fixed::operator -= ( int64_t arg ) { return ( *this -= fixed(arg) ); }
    inline fixed& fixed::operator *= ( int64_t arg ) { return ( *this *= fixed(arg) ); }
    inline fixed& fixed::operator /= ( int64_t arg ) { return ( *this /= fixed(arg) ); }

    inline fixed fixed::operator + ( int64_t arg ) const { return *this + fixed(arg); }
    inline fixed fixed::operator - ( int64_t arg ) const { return *this - fixed(arg); }
    inline fixed fixed::operator * ( int64_t arg ) const { return *this * fixed(arg); }
    inline fixed fixed::operator / ( int64_t arg ) const { return *this / fixed(arg); }

    inline fixed& fixed::operator += ( uint64_t arg ) { return ( *this += fixed(arg) ); }
    inline fixed& fixed::operator -= ( uint64_t arg ) { return ( *this -= fixed(arg) ); }
    inline fixed& fixed::operator *= ( uint64_t arg ) { return ( *this *= fixed(arg) ); }
    inline fixed& fixed::operator /= ( uint64_t arg ) { return ( *this /= fixed(arg) ); }

    inline fixed fixed::operator + ( uint64_t arg ) const { return *this + fixed(arg); }
    inline fixed fixed::operator - ( uint64_t arg ) const { return *this - fixed(arg); }
    inline fixed fixed::operator * ( uint64_t arg ) const { return *this * fixed(arg); }
    inline fixed fixed::operator / ( uint64_t arg ) const { return *this / fixed(arg); }

    inline fixed& fixed::operator += ( int32_t arg ) { return ( *this += fixed(arg) ); }
    inline fixed& fixed::operator -= ( int32_t arg ) { return ( *this -= fixed(arg) ); }
    inline fixed& fixed::operator *= ( int32_t arg ) { return ( *this *= fixed(arg) ); }
    inline fixed& fixed::operator /= ( int32_t arg ) { return ( *this /= fixed(arg) ); }

    inline fixed fixed::operator + ( int32_t arg ) const { return *this + fixed(arg); }
    inline fixed fixed::operator - ( int32_t arg ) const { return *this - fixed(arg); }
    inline fixed fixed::operator * ( int32_t arg ) const { return *this * fixed(arg); }
    inline fixed fixed::operator / ( int32_t arg ) const { return *this / fixed(arg); }

    inline fixed& fixed::operator += ( uint32_t arg ) { return ( *this += fixed(arg) ); }
    inline fixed& fixed::operator -= ( uint32_t arg ) { return ( *this -= fixed(arg) ); }
    inline fixed& fixed::operator *= ( uint32_t arg ) { return ( *this *= fixed(arg) ); }
    inline fixed& fixed::operator /= ( uint32_t arg ) { return ( *this /= fixed(arg) ); }

    inline fixed fixed::operator + ( uint32_t arg ) const { return *this + fixed(arg); }
    inline fixed fixed::operator - ( uint32_t arg ) const { return *this - fixed(arg); }
    inline fixed fixed::operator * ( uint32_t arg ) const { return *this * fixed(arg); }
    inline fixed fixed::operator / ( uint32_t arg ) const { return *this / fixed(arg); }

    inline bool fixed::operator == ( double arg ) const { return *this == fixed(arg); }
    inline bool fixed::operator != ( double arg ) const { return *this != fixed(arg); }
    inline bool fixed::operator <= ( double arg ) const { return *this <= fixed(arg); }
    inline bool fixed::operator <  ( double arg ) const { return *this <  fixed(arg); }
    inline bool fixed::operator >= ( double arg ) const { return *this >= fixed(arg); }
    inline bool fixed::operator >  ( double arg ) const { return *this >  fixed(arg); }

    inline bool fixed::operator == ( float arg ) const { return *this == fixed(arg); }
    inline bool fixed::operator != ( float arg ) const { return *this != fixed(arg); }
    inline bool fixed::operator <= ( float arg ) const { return *this <= fixed(arg); }
    inline bool fixed::operator <  ( float arg ) const { return *this <  fixed(arg); }
    inline bool fixed::operator >= ( float arg ) const { return *this >= fixed(arg); }
    inline bool fixed::operator >  ( float arg ) const { return *this >  fixed(arg); }

    inline bool fixed::operator == ( int64_t arg ) const { return *this == fixed(arg); }
    inline bool fixed::operator != ( int64_t arg ) const { return *this != fixed(arg); }
    inline bool fixed::operator <= ( int64_t arg ) const { return *this <= fixed(arg); }
    inline bool fixed::operator <  ( int64_t arg ) const { return *this <  fixed(arg); }
    inline bool fixed::operator >= ( int64_t arg ) const { return *this >= fixed(arg); }
    inline bool fixed::operator >  ( int64_t arg ) const { return *this >  fixed(arg); }

    inline bool fixed::operator == ( uint64_t arg ) const { return *this == fixed(arg); }
    inline bool fixed::operator != ( uint64_t arg ) const { return *this != fixed(arg); }
    inline bool fixed::operator <= ( uint64_t arg ) const { return *this <= fixed(arg); }
    inline bool fixed::operator <  ( uint64_t arg ) const { return *this <  fixed(arg); }
    inline bool fixed::operator >= ( uint64_t arg ) const { return *this >= fixed(arg); }
    inline bool fixed::operator >  ( uint64_t arg ) const { return *this >  fixed(arg); }

    inline bool fixed::operator == ( int32_t arg ) const { return *this == fixed(arg); }
    inline bool fixed::operator != ( int32_t arg ) const { return *this != fixed(arg); }
    inline bool fixed::operator <= ( int32_t arg ) const { return *this <= fixed(arg); }
    inline bool fixed::operator <  ( int32_t arg ) const { return *this <  fixed(arg); }
    inline bool fixed::operator >= ( int32_t arg ) const { return *this >= fixed(arg); }
    inline bool fixed::operator >  ( int32_t arg ) const { return *this >  fixed(arg); }

    inline bool fixed::operator == ( uint32_t arg ) const { return *this == fixed(arg); }
    inline bool fixed::operator != ( uint32_t arg ) const { return *this != fixed(arg); }
    inline bool fixed::operator <= ( uint32_t arg ) const { return *this <= fixed(arg); }
    inline bool fixed::operator <  ( uint32_t arg ) const { return *this <  fixed(arg); }
    inline bool fixed::operator >= ( uint32_t arg ) const { return *this >= fixed(arg); }
    inline bool fixed::operator >  ( uint32_t arg ) const { return *this >  fixed(arg); }

    inline fixed operator + ( double left, const fixed& right ) { return fixed(left) + right; }
    inline fixed operator - ( double left, const fixed& right ) { return fixed(left) - right; }
    inline fixed operator * ( double left, const fixed& right ) { return fixed(left) * right; }
    inline fixed operator / ( double left, const fixed& right ) { return fixed(left) / right; }

    inline fixed operator + ( float left, const fixed& right ) { return fixed(left) + right; }
    inline fixed operator - ( float left, const fixed& right ) { return fixed(left) - right; }
    inline fixed operator * ( float left, const fixed& right ) { return fixed(left) * right; }
    inline fixed operator / ( float left, const fixed& right ) { return fixed(left) / right; }

    inline fixed operator + ( int64_t left, const fixed& right ) { return fixed(left) + right; }
    inline fixed operator - ( int64_t left, const fixed& right ) { return fixed(left) - right; }
    inline fixed operator * ( int64_t left, const fixed& right ) { return fixed(left) * right; }
    inline fixed operator / ( int64_t left, const fixed& right ) { return fixed(left) / right; }

    inline fixed operator + ( int32_t left, const fixed& right ) { return fixed(left) + right; }
    inline fixed operator - ( int32_t left, const fixed& right ) { return fixed(left) - right; }
    inline fixed operator * ( int32_t left, const fixed& right ) { return fixed(left) * right; }
    inline fixed operator / ( int32_t left, const fixed& right ) { return fixed(left) / right; }

    inline fixed operator + ( uint32_t left, const fixed& right ) { return fixed(left) + right; }
    inline fixed operator - ( uint32_t left, const fixed& right ) { return fixed(left) - right; }
    inline fixed operator * ( uint32_t left, const fixed& right ) { return fixed(left) * right; }
    inline fixed operator / ( uint32_t left, const fixed& right ) { return fixed(left) / right; }

    inline bool operator == ( double left, const fixed& right ) { return fixed(left) == right; }
    inline bool operator != ( double left, const fixed& right ) { return fixed(left) != right; }
    inline bool operator <= ( double left, const fixed& right ) { return fixed(left) <= right; }
    inline bool operator <  ( double left, const fixed& right ) { return fixed(left) <  right; }
    inline bool operator >= ( double left, const fixed& right ) { return fixed(left) >= right; }
    inline bool operator >  ( double left, const fixed& right ) { return fixed(left) >  right; }

    inline bool operator == ( float left, const fixed& right ) { return fixed(left) == right; }
    inline bool operator != ( float left, const fixed& right ) { return fixed(left) != right; }
    inline bool operator <= ( float left, const fixed& right ) { return fixed(left) <= right; }
    inline bool operator <  ( float left, const fixed& right ) { return fixed(left) <  right; }
    inline bool operator >= ( float left, const fixed& right ) { return fixed(left) >= right; }
    inline bool operator >  ( float left, const fixed& right ) { return fixed(left) >  right; }

    inline bool operator == ( int64_t left, const fixed& right ) { return fixed(left) == right; }
    inline bool operator != ( int64_t left, const fixed& right ) { return fixed(left) != right; }
    inline bool operator <= ( int64_t left, const fixed& right ) { return fixed(left) <= right; }
    inline bool operator <  ( int64_t left, const fixed& right ) { return fixed(left) <  right; }
    inline bool operator >= ( int64_t left, const fixed& right ) { return fixed(left) >= right; }
    inline bool operator >  ( int64_t left, const fixed& right ) { return fixed(left) >  right; }

    inline bool operator == ( int32_t left, const fixed& right ) { return fixed(left) == right; }
    inline bool operator != ( int32_t left, const fixed& right ) { return fixed(left) != right; }
    inline bool operator <= ( int32_t left, const fixed& right ) { return fixed(left) <= right; }
    inline bool operator <  ( int32_t left, const fixed& right ) { return fixed(left) <  right; }
    inline bool operator >= ( int32_t left, const fixed& right ) { return fixed(left) >= right; }
    inline bool operator >  ( int32_t left, const fixed& right ) { return fixed(left) >  right; }

    inline bool operator == ( uint32_t left, const fixed& right ) { return fixed(left) == right; }
    inline bool operator != ( uint32_t left, const fixed& right ) { return fixed(left) != right; }
    inline bool operator <= ( uint32_t left, const fixed& right ) { return fixed(left) <= right; }
    inline bool operator <  ( uint32_t left, const fixed& right ) { return fixed(left) <  right; }
    inline bool operator >= ( uint32_t left, const fixed& right ) { return fixed(left) >= right; }
    inline bool operator >  ( uint32_t left, const fixed& right ) { return fixed(left) >  right; }
}
